// Copyright 2011-2012, Vanya Davidenko.
// Используемая кодировка: UTF-8.

#ifndef LIBV_TESTS_BOOST_SMART_ENUM_TESTS_H_
#define LIBV_TESTS_BOOST_SMART_ENUM_TESTS_H_

#include <set>
#include <vector>
#include <gtest/gtest.h>
#include <libv/boost/smart_enum.h>
#include <libv/stdint.h>




namespace libv {
namespace tests {

// Должен сработать static_assert.
// LIBV_DEFINE_SMART_ENUM_STRICT(E8_error_overflow, 
//     (A0)(A1)(A2)(A3)(A4)(A5)(A6)(A7)(A8)(A9)(A10)(A11)(A12)(A13)(A14)(A15)
//     (A16)(A17)(A18)(A19)(A20)(A21)(A22)(A23)(A24)(A25)(A26)(A27)(A28)(A29)
//     (A30)(A31)(A32)(A33)(A34)(A35)(A36)(A37)(A38)(A39)(A40)(A41)(A42)(A43)
//     (A44)(A45)(A46)(A47)(A48)(A49)(A50)(A51)(A52)(A53)(A54)(A55)(A56)(A57)
//     (A58)(A59)(A60)(A61)(A62)(A63)(A64)(A65)(A66)(A67)(A68)(A69)(A70)(A71)
//     (A72)(A73)(A74)(A75)(A76)(A77)(A78)(A79)(A80)(A81)(A82)(A83)(A84)(A85)
//     (A86)(A87)(A88)(A89)(A90)(A91)(A92)(A93)(A94)(A95)(A96)(A97)(A98)(A99)
//     (A100)(A101)(A102)(A103)(A104)(A105)(A106)(A107)(A108)(A109)(A110)
//     (A111)(A112)(A113)(A114)(A115)(A116)(A117)(A118)(A119)(A120)(A121)
//     (A122)(A123)(A124)(A125)(A126)(A127), int8_t);

// Должен сработать static_assert.
// LIBV_DEFINE_SMART_ENUM_STRICT(E_error_not_integral, (A)(B), float);
// 
LIBV_DEFINE_SMART_ENUM_STRICT(E8_no_overflow, 
    (A0)(A1)(A2)(A3)(A4)(A5)(A6)(A7)(A8)(A9)(A10)(A11)(A12)(A13)(A14)(A15)
    (A16)(A17)(A18)(A19)(A20)(A21)(A22)(A23)(A24)(A25)(A26)(A27)(A28)(A29)
    (A30)(A31)(A32)(A33)(A34)(A35)(A36)(A37)(A38)(A39)(A40)(A41)(A42)(A43)
    (A44)(A45)(A46)(A47)(A48)(A49)(A50)(A51)(A52)(A53)(A54)(A55)(A56)(A57)
    (A58)(A59)(A60)(A61)(A62)(A63)(A64)(A65)(A66)(A67)(A68)(A69)(A70)(A71)
    (A72)(A73)(A74)(A75)(A76)(A77)(A78)(A79)(A80)(A81)(A82)(A83)(A84)(A85)
    (A86)(A87)(A88)(A89)(A90)(A91)(A92)(A93)(A94)(A95)(A96)(A97)(A98)(A99)
    (A100)(A101)(A102)(A103)(A104)(A105)(A106)(A107)(A108)(A109)(A110)
    (A111)(A112)(A113)(A114)(A115)(A116)(A117)(A118)(A119)(A120)(A121)
    (A122)(A123)(A124)(A125)(A126), int8_t);



class SmartEnumTests : public ::testing::Test {
  protected:
    virtual void SetUp() {}
    virtual void TearDown() {}

    LIBV_DEFINE_SMART_ENUM(SmartEnum, (A)(B)(C)(D));
};

// В MSVC при использовании связки gtest (у которого все на макросах) 
// и libv smart enum возникает ошибка — undefined variable __LINE__Var.
// Это, похоже, связано с ошибкой в препроцессоре.
LIBV_DEFINE_SMART_ENUM_STRICT(E8u, (A)(B), uint8_t);
LIBV_DEFINE_SMART_ENUM_STRICT(E32u, (A), uint32_t);
LIBV_DEFINE_SMART_ENUM_STRICT(E64,(A)(B)(C), int64_t);

TEST_F(SmartEnumTests, SizeOf) {
    EXPECT_EQ(sizeof(uint8_t), sizeof(E8u));
    EXPECT_EQ(sizeof(uint32_t), sizeof(E32u));
    EXPECT_EQ(sizeof(int64_t), sizeof(E64));
    EXPECT_EQ(sizeof(int), sizeof(SmartEnum));
}


TEST_F(SmartEnumTests, MinMax) {
    EXPECT_EQ(SmartEnum::kUndefined, SmartEnum::kMin);
    EXPECT_EQ(SmartEnum::D, SmartEnum::kMax);
    EXPECT_LT(SmartEnum::kMin, SmartEnum::kMax);
}


TEST_F(SmartEnumTests, IsValidValue) {
    EXPECT_TRUE(SmartEnum::IsValidValue(static_cast<int>(SmartEnum::A)));

    const int u = SmartEnum::kUndefined;
    EXPECT_TRUE(SmartEnum::IsValidValue(u));
    EXPECT_TRUE(SmartEnum::IsValidValue(static_cast<int>(SmartEnum::kMin)));
    EXPECT_TRUE(SmartEnum::IsValidValue(static_cast<int>(SmartEnum::kMax)));

    EXPECT_FALSE(SmartEnum::IsValidValue(static_cast<int>(SmartEnum::kMin-1)));
    EXPECT_FALSE(SmartEnum::IsValidValue(static_cast<int>(SmartEnum::kMax+1)));
}


TEST_F(SmartEnumTests, Ctors) {
    EXPECT_EQ(SmartEnum::kUndefined, SmartEnum());
    EXPECT_EQ(SmartEnum::A, SmartEnum(SmartEnum::A));
    EXPECT_EQ(SmartEnum(SmartEnum::C), SmartEnum(SmartEnum::C));
    EXPECT_EQ(SmartEnum(SmartEnum::D), SmartEnum(SmartEnum(SmartEnum::D)));
    
    // operator=
    SmartEnum b = SmartEnum(SmartEnum::kUndefined);
    b = SmartEnum::B;
    EXPECT_EQ(SmartEnum(SmartEnum::B), b);

    // Не надо обманывать!
    EXPECT_THROW(SmartEnum(static_cast<SmartEnum::Impl>(SmartEnum::kMin - 1)), 
                 libv::PrecondException);

    // На этих 2 строках MSVC глючит, и вместо
    // EXPECT_THROW(SmartEnum(hack) ...
    // он видит
    // EXPECT_THROW(SmartEnum() ...
    // Судя по результатам теста и предупреждению о неиспользованной переменной.
//     SmartEnum::Impl hack = static_cast<SmartEnum::Impl>(SmartEnum::kMin - 1);
//     EXPECT_THROW(SmartEnum(hack), libv::PrecondException);

    // Строгая типизация должна не позволять эти 2 операции.
    //enum X { A };
    //SmartEnum smart_enum_is_type_safe = SmartEnum(X::A);
    //SmartEnum no_implicit_convesion_from_int = SmartEnum(0);
}


TEST_F(SmartEnumTests, FromInt) {
     const int a = SmartEnum::A;
     const int d = SmartEnum::D;
     EXPECT_EQ(SmartEnum::A, SmartEnum::FromInt(a));
     EXPECT_EQ(SmartEnum::D, SmartEnum::FromInt(d));

     const int undefined = SmartEnum::kUndefined;
     const int min = SmartEnum::kMin;
     const int max = SmartEnum::kMax;
     EXPECT_EQ(SmartEnum::kUndefined, SmartEnum::FromInt(undefined));
     EXPECT_EQ(SmartEnum::kMin, SmartEnum::FromInt(min));
     EXPECT_EQ(SmartEnum::kMax, SmartEnum::FromInt(max));

     EXPECT_THROW(SmartEnum::FromInt(static_cast<int>(SmartEnum::kMin - 1)),
                  libv::PrecondException);
     EXPECT_THROW(SmartEnum::FromInt(static_cast<int>(SmartEnum::kMax + 1)),
                  libv::PrecondException);
}


TEST_F(SmartEnumTests, FromString) {
     EXPECT_EQ(SmartEnum::A, SmartEnum::FromString("A"));
     EXPECT_EQ(SmartEnum::C, SmartEnum::FromString("C"));

     EXPECT_EQ(SmartEnum::kUndefined, SmartEnum::FromString(NULL));
     EXPECT_EQ(SmartEnum::kUndefined, SmartEnum::FromString("kUndefined"));
     EXPECT_EQ(SmartEnum::kMin, SmartEnum::FromString("kMin"));
     EXPECT_EQ(SmartEnum::kMax, SmartEnum::FromString("kMax"));

     EXPECT_THROW(SmartEnum::FromString(""), libv::PrecondException);
     EXPECT_THROW(SmartEnum::FromString("XXX"), libv::PrecondException);
}


TEST_F(SmartEnumTests, Equality) {
     EXPECT_TRUE(SmartEnum::kMin == SmartEnum(SmartEnum::kUndefined));
     EXPECT_TRUE(SmartEnum::A == SmartEnum(SmartEnum::A));
     EXPECT_FALSE(SmartEnum::B != SmartEnum(SmartEnum::B));
}


TEST_F(SmartEnumTests, Less) {
     EXPECT_TRUE(SmartEnum::A < SmartEnum(SmartEnum::B));
     EXPECT_TRUE(SmartEnum::A < SmartEnum(SmartEnum::kMax));
     EXPECT_TRUE(SmartEnum::kMin < SmartEnum(SmartEnum::kMax));

     EXPECT_FALSE(SmartEnum::kMin < SmartEnum(SmartEnum::kUndefined));
     EXPECT_FALSE(SmartEnum::kMax < SmartEnum(SmartEnum::kMin));
     EXPECT_FALSE(SmartEnum::kMax < SmartEnum(SmartEnum::kMax));
}


TEST_F(SmartEnumTests, StandardContainers) {
    ::std::vector<SmartEnum> v;
    ::std::set<SmartEnum> s;
}


TEST_F(SmartEnumTests, ToInt) {
     EXPECT_EQ(static_cast<int>(SmartEnum::kUndefined), 
               SmartEnum(SmartEnum::kUndefined).ToInt());
     EXPECT_EQ(static_cast<int>(SmartEnum::kMin), 
               SmartEnum(SmartEnum::kMin).ToInt());
     EXPECT_EQ(static_cast<int>(SmartEnum::kMax),
               SmartEnum(SmartEnum::kMax).ToInt());

     EXPECT_EQ(static_cast<int>(SmartEnum::A), SmartEnum(SmartEnum::A).ToInt());
}


TEST_F(SmartEnumTests, ToString) {
     EXPECT_STREQ("A", SmartEnum(SmartEnum::A).ToString());

     EXPECT_STREQ("kUndefined", SmartEnum(SmartEnum::kUndefined).ToString());
     EXPECT_STREQ("kUndefined", SmartEnum(SmartEnum::kMin).ToString());

     EXPECT_STREQ("D", SmartEnum(SmartEnum::D).ToString());
     EXPECT_STREQ("D", SmartEnum(SmartEnum::kMax).ToString());
}


TEST_F(SmartEnumTests, PrevNext) {
     EXPECT_EQ(SmartEnum::B, SmartEnum(SmartEnum::A).Next());
     EXPECT_EQ(SmartEnum::A, SmartEnum(SmartEnum::B).Prev());

     EXPECT_EQ(SmartEnum::kMin, SmartEnum(SmartEnum::kMax).Next());
     EXPECT_EQ(SmartEnum::kMax, SmartEnum(SmartEnum::kMin).Prev());
}





////////////////////////////////////////////////////////////////////////////////



// Должен сработать static_assert — одной из значений (128) переполняет int8_t.
// LIBV_DEFINE_SMART_ENUM_USER_VALUES_STRICT(Euv8_error_overflow, 
//     ((A0,0))((A1,1))((A2,2))((A3,3))((A4,4))((A5,5))((A6,6))((A7,7))((A8,8))
//     ((A9,9))((A10,10))((A11,11))((A12,12))((A13,13))((A14,14))((A15,15))
//     ((A16,16))((A17,17))((A18,18))((A19,19))((A20,20))((A21,21))((A22,22))
//     ((A23,23))((A24,24))((A25,25))((A26,26))((A27,27))((A28,28))((A29,29))
//     ((A30,30))((A31,31))((A32,32))((A33,33))((A34,34))((A35,35))((A36,36))
//     ((A37,37))((A38,38))((A39,39))((A40,40))((A41,41))((A42,42))((A43,43))
//     ((A44,44))((A45,45))((A46,46))((A47,47))((A48,48))((A49,49))((A50,50))
//     ((A51,51))((A52,52))((A53,53))((A54,54))((A55,55))((A56,56))((A57,57))
//     ((A58,58))((A59,59))((A60,60))((A61,61))((A62,62))((A63,63))((A64,64))
//     ((A65,65))((A66,66))((A67,67))((A68,68))((A69,69))((A70,70))((A71,71))
//     ((A72,72))((A73,73))((A74,74))((A75,75))((A76,76))((A77,77))((A78,78))
//     ((A79,79))((A80,80))((A81,81))((A82,82))((A83,83))((A84,84))((A85,85))
//     ((A86,86))((A87,87))((A88,88))((A89,89))((A90,90))((A91,91))((A92,92))
//     ((A93,93))((A94,94))((A95,95))((A96,96))((A97,97))((A98,98))((A99,99))
//     ((A100,100))((A101,101))((A102,102))((A103,103))((A104,104))((A105,105))
//     ((A106,106))((A107,107))((A108,108))((A109,109))((A110,110))((A111,111))
//     ((A112,112))((A113,113))((A114,114))((A115,115))((A116,116))((A117,117))
//     ((A118,118))((A119,119))((A120,120))((A121,121))((A122,122))((A123,123))
//     ((A124,124))((A125,125))((A126,126))((A127,127))((A128,128)), 
//     -1, int8_t);

// Должен сработать static_assert — undefined value (128) переполняет int8_t.
// LIBV_DEFINE_SMART_ENUM_USER_VALUES_STRICT(Euv8_error_undef_value_overflow, 
//     ((A0,0))((A1,1)), 128, int8_t);

// Должен сработать static_assert — нельзя использовать double для хранения.
// LIBV_DEFINE_SMART_ENUM_USER_VALUES_STRICT(Euv8_error_not_int_storage_type, 
//     ((A0,0))((A1,1)), 3, double);

class SmartEnumUserValuesTests : public ::testing::Test {
  protected:
    virtual void SetUp() {}
    virtual void TearDown() {}

    LIBV_DEFINE_SMART_ENUM_USER_VALUES(SmartEnum, 
                                       ((A,1))((B,2))((C,4))((D,8)), 
                                       -1);
};

// В MSVC при использовании связки gtest (у которого все на макросах) 
// и libv smart enum возникает ошибка — undefined variable __LINE__Var.
// Это, похоже, связано с ошибкой в препроцессоре.
LIBV_DEFINE_SMART_ENUM_USER_VALUES_STRICT(EUV8u, ((A,1))((B,2)), 0, uint8_t);
LIBV_DEFINE_SMART_ENUM_USER_VALUES_STRICT(EUV32u, ((A,2)), 0, uint32_t);
LIBV_DEFINE_SMART_ENUM_USER_VALUES_STRICT(EUV64, ((A,1))((B,2)), 0, int64_t);

TEST_F(SmartEnumUserValuesTests, SizeOf) {
    EXPECT_EQ(sizeof(uint8_t), sizeof(EUV8u));
    EXPECT_EQ(sizeof(uint32_t), sizeof(EUV32u));
    EXPECT_EQ(sizeof(int64_t), sizeof(EUV64));
    EXPECT_EQ(sizeof(int), sizeof(SmartEnum));
}


TEST_F(SmartEnumUserValuesTests, IsValidValue) {
    EXPECT_TRUE(SmartEnum::IsValidValue(static_cast<int>(SmartEnum::A)));
    EXPECT_TRUE(SmartEnum::IsValidValue(static_cast<int>(SmartEnum::D)));

    const int u = SmartEnum::kUndefined;
    EXPECT_TRUE(SmartEnum::IsValidValue(u));
}


TEST_F(SmartEnumUserValuesTests, Undefined) {
    EXPECT_EQ(-1, SmartEnum::kUndefined);
    EXPECT_EQ(SmartEnum::kUndefined, SmartEnum());
}


TEST_F(SmartEnumUserValuesTests, Ctors) {
    EXPECT_EQ(SmartEnum::kUndefined, SmartEnum());
    EXPECT_EQ(SmartEnum::A, SmartEnum(SmartEnum::A));
    EXPECT_EQ(SmartEnum(SmartEnum::C), SmartEnum(SmartEnum::C));
    EXPECT_EQ(SmartEnum(SmartEnum::D), SmartEnum(SmartEnum(SmartEnum::D)));
    
    // operator=
    SmartEnum b = SmartEnum(SmartEnum::kUndefined);
    b = SmartEnum::B;
    EXPECT_EQ(SmartEnum(SmartEnum::B), b);

    // Не надо обманывать!
    EXPECT_THROW(SmartEnum(static_cast<SmartEnum::Impl>(666)), 
                 libv::PrecondException);

    // Строгая типизация должна не позволять эти 2 операции.
    //enum X { A };
    //SmartEnum smart_enum_is_type_safe = SmartEnum(X::A);
    //SmartEnum no_implicit_convesion_from_int = SmartEnum(0);
}


TEST_F(SmartEnumUserValuesTests, FromInt) {
     EXPECT_EQ(SmartEnum::A, SmartEnum::FromInt(1));

     const int a = SmartEnum::A;
     const int d = SmartEnum::D;
     EXPECT_EQ(SmartEnum::A, SmartEnum::FromInt(a));
     EXPECT_EQ(SmartEnum::D, SmartEnum::FromInt(d));

     const int undefined = SmartEnum::kUndefined;
     EXPECT_EQ(SmartEnum::kUndefined, SmartEnum::FromInt(undefined));

     EXPECT_THROW(SmartEnum::FromInt(static_cast<int>(666)),
                  libv::PrecondException);
}


TEST_F(SmartEnumUserValuesTests, FromString) {
     EXPECT_EQ(SmartEnum::A, SmartEnum::FromString("A"));
     EXPECT_EQ(SmartEnum::C, SmartEnum::FromString("C"));

     EXPECT_EQ(SmartEnum::kUndefined, SmartEnum::FromString(NULL));
     EXPECT_EQ(SmartEnum::kUndefined, SmartEnum::FromString("kUndefined"));

     EXPECT_THROW(SmartEnum::FromString(""), libv::PrecondException);
     EXPECT_THROW(SmartEnum::FromString("XXX"), libv::PrecondException);
}


TEST_F(SmartEnumUserValuesTests, Equality) {
     EXPECT_TRUE(SmartEnum::kUndefined == SmartEnum(SmartEnum::kUndefined));
     EXPECT_TRUE(SmartEnum::A == SmartEnum(SmartEnum::A));
     EXPECT_FALSE(SmartEnum::B != SmartEnum(SmartEnum::B));
}


TEST_F(SmartEnumUserValuesTests, Less) {
     EXPECT_TRUE(SmartEnum::A < SmartEnum(SmartEnum::B));
     EXPECT_TRUE(SmartEnum::A < SmartEnum(SmartEnum::D));

     EXPECT_FALSE(SmartEnum::kUndefined < SmartEnum(SmartEnum::kUndefined));
     EXPECT_FALSE(SmartEnum::A < SmartEnum(SmartEnum::A));
}


TEST_F(SmartEnumUserValuesTests, StandardContainers) {
    ::std::vector<SmartEnum> v;
    ::std::set<SmartEnum> s;
}


TEST_F(SmartEnumUserValuesTests, ToInt) {
     EXPECT_EQ(static_cast<int>(SmartEnum::kUndefined), 
               SmartEnum(SmartEnum::kUndefined).ToInt());

     EXPECT_EQ(1, SmartEnum(SmartEnum::A).ToInt());
     EXPECT_EQ(static_cast<int>(SmartEnum::A), SmartEnum(SmartEnum::A).ToInt());
}


TEST_F(SmartEnumUserValuesTests, ToString) {
     EXPECT_STREQ("A", SmartEnum(SmartEnum::A).ToString());
     EXPECT_STREQ("D", SmartEnum(SmartEnum::D).ToString());
     EXPECT_STREQ("kUndefined", SmartEnum(SmartEnum::kUndefined).ToString());
}


TEST_F(SmartEnumUserValuesTests, Next) {
     EXPECT_EQ(SmartEnum::B, SmartEnum(SmartEnum::A).Next());
     EXPECT_EQ(SmartEnum::C, SmartEnum(SmartEnum::A).Next().Next());

     EXPECT_EQ(SmartEnum::B, SmartEnum(SmartEnum::B).Next().Prev());

     EXPECT_EQ(SmartEnum::A, SmartEnum(SmartEnum::kUndefined).Next());
     EXPECT_EQ(SmartEnum::kUndefined, SmartEnum(SmartEnum::D).Next());
}


TEST_F(SmartEnumUserValuesTests, Prev) {
     EXPECT_EQ(SmartEnum::C, SmartEnum(SmartEnum::D).Prev());
     EXPECT_EQ(SmartEnum::A, SmartEnum(SmartEnum::D).Prev().Prev().Prev());

     EXPECT_EQ(SmartEnum::B, SmartEnum(SmartEnum::B).Prev().Next());

     EXPECT_EQ(SmartEnum::D, SmartEnum(SmartEnum::kUndefined).Prev());
     EXPECT_EQ(SmartEnum::kUndefined, SmartEnum(SmartEnum::A).Prev());
}

}  // tests
}  // libv

#endif  // LIBV_TESTS_BOOST_SMART_ENUM_TESTS_H_
